package com.chixing.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alipay.api.domain.Product;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.injector.methods.SelectOne;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chixing.entity.*;
import com.chixing.entity.VO.HotelSearchVO;
import com.chixing.mapper.*;
import com.chixing.service.IHotelService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chixing.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;



import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;


import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;


import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;


import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit;


import java.util.concurrent.TimeUnit;


import java.util.concurrent.TimeUnit;



import java.util.stream.Collectors;

import java.util.stream.Stream;


import java.util.stream.Collectors;


import java.util.concurrent.TimeUnit;

import java.util.stream.Collectors;




/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author smith
 * @since 2025-09-30
 */
@Slf4j
@Service
public class HotelServiceImpl  implements IHotelService {

    @Autowired
    private HotelMapper hotelMapper;
    @Autowired
    private HotelImagesMapper hotelImagesMapper;
    @Autowired
    private HotelFacilitiesRelationMapper hotelFacilitiesRelationMapper;
    @Autowired
    private HotelFacilitiesMapper hotelFacilitiesMapper;
    @Autowired
    private HotelPoliciesMapper hotelPoliciesMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CommentMapper commentMapper;
//    @Autowired
//    private RedisTemplate redisTemplate;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public Result getName(String hotelName) {
        QueryWrapper<Hotel> wrapper = new QueryWrapper<>();
        wrapper.like("hotel_name",hotelName);
        List<Hotel> hotelList = hotelMapper.selectList(wrapper);
        if (hotelList!= null && hotelList.size()>0)
            return Result.getSuccess(hotelList);
        return Result.getFail(null);
    }

    @Override
    public Result getById(Long hotelId) {
        Hotel hotel = hotelMapper.selectById(hotelId);
        if (hotel!=null)
            return Result.getSuccess(hotel);
        return Result.getFail(null);
    }


    @Override
    public Result getByHotelId(Integer hotelId) {
        Map<String,Object> map = new HashMap<>();
        //1.hotel查询酒店名称地址最低价格
        Hotel hotel = hotelMapper.selectById(hotelId);
         map.put("hotelId",hotel.getHotelId());
         map.put("hotelName",hotel.getHotelName());
         map.put("city",hotel.getCity());
         map.put("county",hotel.getCounty());
         map.put("street",hotel.getStreet());
         map.put("minRoomPrice",hotel.getMinRoomPrice());
         map.put("latitude",hotel.getLatitude());
         map.put("longitude",hotel.getLongitude());
        //2.hotel_images酒店的图片
        QueryWrapper<HotelImages> hotelImagesQueryWrapper = new QueryWrapper<>();
        hotelImagesQueryWrapper.eq("hotel_id",hotelId);
        List<HotelImages> hotelImages = hotelImagesMapper.selectList(hotelImagesQueryWrapper);
        map.put("imageUrlList",hotelImages);
        //3.hotel_facilities_relation，hotel_facilities 查询酒店设施的名称
        List<String> facilityNameList = new ArrayList<>();
        QueryWrapper<HotelFacilitiesRelation> hotelFacilitiesRelationQueryWrapper = new QueryWrapper<>();
        hotelFacilitiesRelationQueryWrapper.eq("hotel_id", hotelId);
        List<HotelFacilitiesRelation> hotelFacilitiesRelations = hotelFacilitiesRelationMapper.selectList(hotelFacilitiesRelationQueryWrapper);
        for (HotelFacilitiesRelation relation:hotelFacilitiesRelations) {
            HotelFacilities hotelFacilities = hotelFacilitiesMapper.selectById(relation.getFacilityId());
            facilityNameList.add(hotelFacilities.getFacilityName());
        }
        map.put("facilityNameList",facilityNameList);
        //4.hotel_policies查询酒店政策和酒店政策详情
        List<String> hotelPoliciesList = new ArrayList<>();
        QueryWrapper<HotelPolicies> hotelPoliciesQueryWrapper = new QueryWrapper<>();
        hotelPoliciesQueryWrapper.eq("hotel_id",hotelId);
        List<HotelPolicies> hotelPolicies = hotelPoliciesMapper.selectList(hotelPoliciesQueryWrapper);
        for (HotelPolicies h:hotelPolicies) {
            hotelPoliciesList.add(h.getPolicyType()+ ":" +h.getPolicyContent());
        }
        map.put("hotelPoliciesList",hotelPoliciesList);

        if (map!=null)
            return Result.getSuccess(map);
        return Result.getFail(null);
    }

    @Override
    public Result getHotelsByLocation(String location) {
        System.out.println("查询地点: " + location);

        // 构造查询条件
        QueryWrapper<Hotel> wrapper = new QueryWrapper<>();
        wrapper.eq("is_deleted", 0);

        // 如果 location 不为空，则加上模糊匹配条件
        if (location != null && !location.trim().isEmpty()) {
            wrapper.and(w -> w.like("province", location)
                    .or().like("hotel_name", location)
                    .or().like("city", location)
                    .or().like("county", location)
                    .or().like("street", location));
        }

        // 执行查询
        List<Hotel> hotelList = hotelMapper.selectList(wrapper);
        System.out.println("查询记录数: " + hotelList.size());

        return Result.getSuccess(hotelList);
    }

    @Override
    public Result searchHotels(HotelSearchVO vo) {
        List<Hotel> hotels = hotelMapper.searchHotels(vo);
        return Result.getSuccess(hotels); // hotels 即使为空也返回成功
    }

    @Override
    public List<Hotel> getHotHotel() {
        ListOperations<String,Hotel> operations = redisTemplate.opsForList();
        String key = "hotHotels";
        if (redisTemplate.hasKey(key)) {
            List<Hotel> hotHotelList = operations.range(key, 0, 4);
            return hotHotelList;
        }else {
            QueryWrapper<Hotel> queryWrapper = new QueryWrapper<>();
            queryWrapper.orderByDesc("hotel_rating");
            Page<Hotel> page = new Page<>(1,5);
            page = hotelMapper.selectPage(page, queryWrapper);
            List<Hotel> hotHotelList = page.getRecords();
            operations.rightPushAll(key,hotHotelList);

            redisTemplate.expire(key,30, TimeUnit.MINUTES);//30s过期,从新拉去最新的数据库
            return hotHotelList;
        }
    }

    @Override
    public List<Hotel> getRecommendHotel(Long userId) {
        try {
            // 查询该用户的所有评分记录
            QueryWrapper<Comment> userCommentWrapper = new QueryWrapper<>();
            userCommentWrapper.eq("user_id",userId)
                    .select("hotel_id","rating");
            List<Comment> userComments = commentMapper.selectList(userCommentWrapper);

            if (userComments.isEmpty()) {
                return getHotHotel(); // 用户无评价 => 返回热门酒店
            }

            List<Long> likeHotelIds = userComments.stream()
                    .filter(c -> c.getRating() != null && c.getRating() >= 4)
                    .map(Comment::getHotelId)
                    .distinct()
                    .toList();
            if (likeHotelIds.isEmpty()) {
                return getHotHotel(); // 全部低分 => 返回热门酒店
            }

            QueryWrapper<Comment> similarUserWrapper = new QueryWrapper<>();
            similarUserWrapper.in("hotel_id", likeHotelIds)
                    .ne("user_id", userId)
                    .select("user_id", "hotel_id", "rating");
            List<Comment> similarUsersComments = commentMapper.selectList(similarUserWrapper);

            HashMap<Long, Double> hotelScoreSum = new HashMap<>();
            HashMap<Long, Integer> hotelScoreCount = new HashMap<>();
            for (Comment c : similarUsersComments) {
                if (c.getRating() == null) continue;
                hotelScoreSum.merge(c.getHotelId(), c.getRating(), Double::sum);
                hotelScoreCount.merge(c.getHotelId(), 1, Integer::sum);
            }

            List<Map.Entry<Long, Double>> sortedHotels = hotelScoreSum.entrySet().stream()
                    .map(e -> Map.entry(e.getKey(), e.getValue() / hotelScoreCount.get(e.getKey())))
                    .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                    .limit(5)
                    .toList();

            if (sortedHotels.isEmpty()) {
                return getHotHotel();
            }

            List<Long> recommendIds = sortedHotels.stream()
                    .map(Map.Entry::getKey)
                    .toList();

            return hotelMapper.selectBatchIds(recommendIds);

        } catch (Exception e) {
            log.error("推荐算法异常，返回热门酒店兜底", e);
            return getHotHotel();
        }
    }

    /**
     * 将MySQL中Product的数据保存到Elasticsearch
     */
    @Override
    public void saveProducts2Elasticsearch() {
        QueryWrapper<Hotel> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("hotel_id","hotel_name","province","city","county","street");
        List<Hotel> HotelList = hotelMapper.selectList(queryWrapper);
        System.out.println("查询到的酒店个数：" + HotelList.size());

        elasticsearchTemplate.save(HotelList);
    }

    /**
     * 根据关键字搜索
     * @param keyword
     * @return
     */
    @Override
    public List<Hotel> getByKeyword(String keyword) {
        List<Hotel> HotelList  = new ArrayList<>();

        SearchResponse<Hotel> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(s -> s
                            .index("hotel_index")  // 替换为实际的索引名
                            .query(q -> q
                                    .multiMatch(m -> m
                                            .query(keyword)  // 查询关键词
                                            .fields("hotelName", "province","city","county","street")  // 查询的字段
                                    )
                            ),
                    Hotel.class
            );
        } catch (IOException e) {
            e.printStackTrace();
        }

        List<Hit<Hotel>> hits = searchResponse.hits().hits();
        for (Hit<Hotel> hit : hits){
            HotelList.add(hit.source());
            System.out.println(hit.source());
        }

        return HotelList;
    }

    /**
     * 分词高亮查询，  分页与根据价格升序排序
     * @param keyword
     * @return
     */
    @Override
    public List<Hotel> getByKeywordAndPage(String keyword) {
        List<Hotel> HotelList  = new ArrayList<>();

        SearchResponse<Hotel> searchResponse = null;

        try {
            searchResponse = elasticsearchClient.search(s -> s
                            .index("hotel_index") // 替换为实际的索引名
                            .query(q -> q
                                    .multiMatch(m -> m
                                            .query(keyword)
                                            .fields("hotelName", "province","city","county","street")
                                    )
                            )
/*                            .from((page-1)*5) //设置分页的起始位置
                            .size(5)  //每页文档的数量*/
                            .sort(sort -> sort
                                    .field(f ->f
                                            .field("hotelId")//hotelId 排序
                                            .order(SortOrder.Asc)//// 升序排序，改为 Desc 则是降序
                                    )
                            )
                            .highlight(h -> h
                                    .fields("hotelName",f->f //高亮proName  字段
                                            .preTags("<em style = 'color:red'>")// 高亮标签的起始部分
                                            .postTags("</em>")// 高亮标签的结束部分
                                    )
                                    .fields("province",f->f //高亮proName  字段
                                            .preTags("<em style = 'color:red'>")// 高亮标签的起始部分
                                            .postTags("</em>")// 高亮标签的结束部分
                                    )
                                    .fields("city",f->f
                                            .preTags("<em style = 'color:red'>")// 高亮 proDesc 字段
                                            .postTags("</em>")
                                    )
                                    .fields("county",f->f
                                            .preTags("<em style = 'color:red'>")// 高亮 proDesc 字段
                                            .postTags("</em>")
                                    )
                                    .fields("street",f->f
                                            .preTags("<em style = 'color:red'>")// 高亮 proDesc 字段
                                            .postTags("</em>")
                                    )
                            ),
                    Hotel.class
            );
        }catch (IOException e){
            e.printStackTrace();
        }

        if (searchResponse != null) {
            List<Hit<Hotel>> hits = searchResponse.hits().hits();
            for (Hit<Hotel> hit : hits) {
                Hotel Hotel = hit.source();
                if (Hotel != null) {
                    // 设置高亮结果到产品字段中
                    Map<String, List<String>> highlightFields = hit.highlight();
                    if (highlightFields != null) {
                        // 如果存在高亮结果，替换原始字段值
                        if (highlightFields.containsKey("hotelName")) {
                            Hotel.setHotelName(String.join("", highlightFields.get("hotelName")));
                        }
                        if (highlightFields.containsKey("province")) {
                            Hotel.setProvince(String.join("", highlightFields.get("province")));
                        }
                        if (highlightFields.containsKey("city")) {
                            Hotel.setCity(String.join("", highlightFields.get("city")));
                        }
                        if (highlightFields.containsKey("county")) {
                            Hotel.setCounty(String.join("", highlightFields.get("county")));
                        }
                        if (highlightFields.containsKey("street")) {
                            Hotel.setStreet(String.join("", highlightFields.get("street")));
                        }
                    }
                    HotelList.add(Hotel);
                }
            }
        }

        return HotelList;
    }



    public List<Hotel> searchHotelES(HotelSearchVO vo) {
        // 首页默认房间数为 1，如果前端没传
        if (vo.getRoomCount() == null) {
            vo.setRoomCount(1);
        }

        // 1. ES 高亮查询
        List<Hotel> esHotels = getByKeywordAndPage(vo.getLocation());
        if (esHotels.isEmpty()) return new ArrayList<>();

        // 2. 获取 hotelId 列表
        List<Long> hotelIds = esHotels.stream()
                .map(Hotel::getHotelId)
                .toList();
        vo.setHotelIds(hotelIds);

        // 3. SQL 查询完整酒店信息
        List<Hotel> sqlHotels = hotelMapper.searchHotels(vo);

        // 4. 构建 Map，便于合并
        Map<Long, Hotel> esMap = esHotels.stream()
                .collect(Collectors.toMap(Hotel::getHotelId, h -> h));

        for (Hotel sqlHotel : sqlHotels) {
            Hotel esHotel = esMap.get(sqlHotel.getHotelId());
            if (esHotel != null) {
                // 保留 ES 高亮字段，只覆盖 SQL 的其他字段
                sqlHotel.setHotelName(esHotel.getHotelName()); // 保留高亮
                sqlHotel.setProvince(esHotel.getProvince());
                sqlHotel.setCity(esHotel.getCity());
                sqlHotel.setCounty(esHotel.getCounty());
                sqlHotel.setStreet(esHotel.getStreet());
            } else {
                // SQL 中有，但 ES 没有，直接加入
                esHotels.add(sqlHotel);
            }
        }

        return sqlHotels;
    }
}
